Skip to content

[MWAR-220] filter overlay jars conflicting with managed dependencies - #639

Open
elharo wants to merge 10 commits into
apache:masterfrom
elharo:MWAR-389-fix
Open

[MWAR-220] filter overlay jars conflicting with managed dependencies#639
elharo wants to merge 10 commits into
apache:masterfrom
elharo:MWAR-389-fix

Conversation

@elharo

@elharo elharo commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #389

When a project uses dependencyManagement to pin a specific version of a library, any jar in a WAR overlay's WEB-INF/lib with the same artifactId but a different version will now be removed during overlay processing.

How it works

In OverlayPackagingTask.performPackaging(), after unpacking the overlay and before copying files, the new filterConflictingDependencyJars() method scans the overlay's WEB-INF/lib directory. For each jar, it extracts the artifactId from the filename (following the artifactId-version(-classifier)?.jar convention). If the artifactId matches a non-optional jar artifact from the project's dependency tree, the overlay jar is removed — the managed version will be provided by the project's own dependency resolution.

Additional changes

  • Updated the MWAR-389 IT test (verify.bsh) to remove print statements for the passing case per review feedback.

The IT test in PR #638 documents this issue and now passes with this fix applied.

@elharo elharo changed the title Fix MWAR-220: filter overlay jars conflicting with managed dependencies [MWAR-220] filter overlay jars conflicting with managed dependencies Jul 26, 2026
Comment thread src/it/MWAR-389/verify.bsh Outdated
return false;
}

System.out.println( "Overlay WAR correctly contains only plexus-utils-3.0.24.jar." );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this; passing tests generate no output

Comment thread src/it/MWAR-389/verify.bsh Outdated
if ( mainHasOld )
{
// Both versions found -- issue STILL EXISTS
System.out.println( "ISSUE #389 (MWAR-220) STILL EXISTS:" );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this; passing tests generate no output

Comment thread src/it/MWAR-389/verify.bsh Outdated
// Both versions found -- issue STILL EXISTS
System.out.println( "ISSUE #389 (MWAR-220) STILL EXISTS:" );
System.out.println( " Both plexus-utils versions found in main WAR WEB-INF/lib:" );
System.out.println( " - plexus-utils-3.0.24.jar (from overlay unpacking)" );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this; passing tests generate no output

Comment thread src/it/MWAR-389/verify.bsh Outdated
{
// Both versions found -- issue STILL EXISTS
System.out.println( "ISSUE #389 (MWAR-220) STILL EXISTS:" );
System.out.println( " Both plexus-utils versions found in main WAR WEB-INF/lib:" );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this; passing tests generate no output

Comment thread src/it/MWAR-389/verify.bsh Outdated
System.out.println( " Both plexus-utils versions found in main WAR WEB-INF/lib:" );
System.out.println( " - plexus-utils-3.0.24.jar (from overlay unpacking)" );
System.out.println( " - plexus-utils-" + managedVersion + ".jar (from dependencyManagement)" );
System.out.println( " dependencyManagement does not filter overlay-provided jars." );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this; passing tests generate no output

Comment thread src/it/MWAR-389/verify.bsh Outdated
System.out.println( "ISSUE #389 (MWAR-220) STILL EXISTS:" );
System.out.println( " Both plexus-utils versions found in main WAR WEB-INF/lib:" );
System.out.println( " - plexus-utils-3.0.24.jar (from overlay unpacking)" );
System.out.println( " - plexus-utils-" + managedVersion + ".jar (from dependencyManagement)" );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this; passing tests generate no output

Comment thread src/it/MWAR-389/verify.bsh Outdated
else
{
// Only managed version -- issue has been fixed
System.out.println( "ISSUE #389 (MWAR-220) APPEARS FIXED:" );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this; passing tests generate no output

Comment thread src/it/MWAR-389/verify.bsh Outdated
{
// Only managed version -- issue has been fixed
System.out.println( "ISSUE #389 (MWAR-220) APPEARS FIXED:" );
System.out.println( " Only plexus-utils-" + managedVersion + ".jar found in WEB-INF/lib." );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this; passing tests generate no output

Comment thread src/it/MWAR-389/verify.bsh Outdated
@elharo elharo self-assigned this Jul 26, 2026
@elharo elharo added bug Something isn't working java Pull requests that update Java code labels Jul 26, 2026
@elharo
elharo requested a review from Copilot July 26, 2026 13:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses MWAR-220 / issue #389 by preventing WAR overlays from contributing conflicting library JARs into the final WAR when the consuming project already resolves that dependency (typically via dependencyManagement), so only the project-resolved version remains in WEB-INF/lib.

Changes:

  • Add overlay-side filtering to remove conflicting WEB-INF/lib/*.jar entries during overlay processing.
  • Add a new integration test scenario (src/it/MWAR-389) reproducing the dependencyManagement + overlay version-conflict case.
  • Update the IT verification script to validate the overlay contains the “old” version while the final WAR contains only the managed version.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java Adds overlay WEB-INF/lib filtering logic to remove JARs that conflict with project-resolved dependencies.
src/it/MWAR-389/verify.bsh Verifies the overlay WAR contains the old dependency version and the main WAR contains only the managed version.
src/it/MWAR-389/pom.xml Adds a multi-module IT root POM for the MWAR-389 scenario.
src/it/MWAR-389/overlay-war/src/main/webapp/WEB-INF/web.xml Minimal overlay WAR web.xml for the IT module.
src/it/MWAR-389/overlay-war/pom.xml Defines overlay WAR with a fixed dependency version (plexus-utils 3.0.24).
src/it/MWAR-389/main-war/src/main/webapp/WEB-INF/web.xml Minimal main WAR web.xml for the IT module.
src/it/MWAR-389/main-war/pom.xml Defines main WAR with dependencyManagement-pinned plexus-utils and overlay dependency.
src/it/MWAR-389/invoker.properties Invoker configuration to run the IT build.
Comments suppressed due to low confidence (1)

src/it/MWAR-389/verify.bsh:84

  • mainLib.list() can return null, which will cause a NullPointerException when iterating mainJars. Add the same directory/list guards used elsewhere in the IT suite.
String[] mainJars = mainLib.list();
boolean mainHasOld = false;
boolean mainHasManaged = false;

for ( int i = 0; i < mainJars.length; i++ )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java Outdated
Comment thread src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java Outdated
Comment thread src/it/MWAR-389/verify.bsh
elharo and others added 3 commits July 26, 2026 13:54
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Only consider project artifacts that will actually be packaged into
WEB-INF/lib (runtime scope). Otherwise provided-scope artifacts cause
overlay jars to be removed even though no project jar replaces them,
resulting in a missing dependency at runtime.
- Check overlayJar.delete() return value, log warning on failure
- Guard mainLib.list() against null return in verify.bsh
- Add missing directory existence check for main WAR WEB-INF/lib
@elharo
elharo marked this pull request as draft July 26, 2026 15:12
@elharo
elharo requested a review from Copilot July 26, 2026 18:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:70

  • filterConflictingDependencyJars() deletes JARs directly out of the overlay temp directory (tmpDir). Because unpackOverlay() intentionally caches the unpacked overlay when the overlay artifact timestamp hasn’t changed, those deletions will persist across subsequent builds without a clean — even if the project dependency set changes and the overlay JAR should be included again.
                // Step1b: Remove jars from overlay that conflict with managed dependencies
                filterConflictingDependencyJars(context, tmpDir);

src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:201

  • extractArtifactId() assumes the first digit in the filename marks the start of the version. That fails for artifactIds that themselves contain digits (e.g. commons-lang3 → commons-lang3-3.20.0.jar), causing artifactId extraction to return null and the overlay JAR never to be filtered.
        int versionStart = -1;
        for (int i = 0; i < baseName.length(); i++) {
            if (Character.isDigit(baseName.charAt(i))) {
                versionStart = i;
                break;

src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:176

  • The debug message says the removed dependency is the “managed version”, but the current logic is based on the resolved runtime dependency set and may include non-managed dependencies. Tweaking the message would avoid misleading diagnostics.
                context.getLog()
                        .debug("Removing dependency [" + jarName + "] from overlay [" + overlay.getId()
                                + "]; managed version in project already provides it");

Comment thread src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java Outdated
@elharo
elharo marked this pull request as ready for review July 28, 2026 11:37
@elharo
elharo requested a review from Copilot July 28, 2026 11:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment on lines +176 to +186
Set<String> conflicting = new HashSet<>();
for (File overlayJar : overlayJars) {
String jarName = overlayJar.getName();
String artifactId = extractArtifactId(jarName);
if (artifactId != null && projectArtifactIds.contains(artifactId)) {
context.getLog()
.debug("Excluding dependency [" + jarName + "] from overlay [" + overlay.getId()
+ "]; project runtime dependencies already include a version");
conflicting.add("WEB-INF/lib/" + jarName);
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overlay is a field of OverlayPackagingTask (line 46: private final Overlay overlay;). Since computeConflictingJars is an instance method, this.overlay is accessible in scope. The code compiles and runs correctly.

Comment on lines +159 to +165
ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME);
Set<String> projectArtifactIds = new HashSet<>();
for (Artifact artifact : context.getProject().getArtifacts()) {
if (!artifact.isOptional() && filter.include(artifact) && "jar".equals(artifact.getType())) {
projectArtifactIds.add(artifact.getArtifactId());
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit fc54f70. Added getJarGroupId() which reads META-INF/maven/**/pom.properties from each overlay jar to obtain the groupId for precise groupId:artifactId matching. Falls back to artifactId-only matching when the metadata is absent.

@elharo
elharo marked this pull request as draft July 28, 2026 12:13
@elharo
elharo marked this pull request as ready for review July 28, 2026 12:39
@elharo
elharo requested a review from Copilot July 28, 2026 12:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (3)

src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:193

  • getJarGroupId(overlayJar) opens and scans each jar, even when the extracted artifactId is not present in projectFallbackIds and therefore can never match. A straightforward optimization is to first check projectFallbackIds.contains(artifactId) and only then open the jar to resolve groupId for precise matching.
        for (File overlayJar : overlayJars) {
            String jarName = overlayJar.getName();
            String artifactId = extractArtifactId(jarName);
            if (artifactId == null) {
                continue;
            }

            String groupId = getJarGroupId(overlayJar);
            String key = (groupId != null) ? groupId + ":" + artifactId : artifactId;

src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:230

  • The current approach mixes metadata-based matching (groupId from pom.properties) with filename parsing for artifactId. Since META-INF/maven/<groupId>/<artifactId>/pom.properties already includes both coordinates (and the path itself encodes them), consider extracting both groupId and artifactId from the META-INF/maven/.../pom.properties entry when present, and only falling back to filename parsing when the metadata is absent. This improves correctness for jars whose filenames don’t match artifactId-version... and reduces reliance on heuristics.
    /**
     * Reads the Maven groupId from a jar's {@code META-INF/maven/**\/pom.properties} metadata, if
     * present.
     *
     * @param jarFile the jar file
     * @return the groupId, or null if it could not be determined
     */
    private static String getJarGroupId(File jarFile) {
        try (ZipFile zip = new ZipFile(jarFile)) {
            Enumeration<? extends ZipEntry> entries = zip.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String name = entry.getName();
                if (name.startsWith("META-INF/maven/") && name.endsWith("/pom.properties")) {
                    Properties props = new Properties();
                    try (InputStream is = zip.getInputStream(entry)) {
                        props.load(is);
                    }
                    return props.getProperty("groupId");
                }
            }
        } catch (IOException e) {
            // groupId not available; fall back to artifactId-only matching
        }
        return null;
    }

src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:255

  • The current approach mixes metadata-based matching (groupId from pom.properties) with filename parsing for artifactId. Since META-INF/maven/<groupId>/<artifactId>/pom.properties already includes both coordinates (and the path itself encodes them), consider extracting both groupId and artifactId from the META-INF/maven/.../pom.properties entry when present, and only falling back to filename parsing when the metadata is absent. This improves correctness for jars whose filenames don’t match artifactId-version... and reduces reliance on heuristics.
    /**
     * Extracts the Maven artifactId from a jar filename following the
     * {@code artifactId-version(-classifier)?.jar} convention.
     *

Comment on lines +159 to +163
private Set<String> computeConflictingJars(WarPackagingContext context, File overlayDir) {
File libDir = new File(overlayDir, "WEB-INF/lib");
if (!libDir.isDirectory()) {
return Collections.emptySet();
}
Comment on lines +194 to +200
if ((groupId != null && projectArtifactKeys.contains(key))
|| (groupId == null && projectFallbackIds.contains(artifactId))) {
context.getLog()
.debug("Excluding dependency [" + jarName + "] from overlay [" + overlay.getId()
+ "]; project runtime dependencies already include a version");
conflicting.add("WEB-INF/lib/" + jarName);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working java Pull requests that update Java code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MWAR-220] dependencyManagement && overlay + transitive dependency = multiple versions of same dependency in WEB-INF/lib

2 participants